932. Beautiful Array
#Algorithm #Algorithm-Divide_N_Conquer
1. 문제
1-1. 원문
An array nums
of length n
is beautiful if:
nums
is a permutation of the integers in the range[1, n]
.- For every
0 <= i < j < n
, there is no indexk
withi < k < j
where2 * nums[k] == nums[i] + nums[j]
.
Given the integer n
, return any beautiful array nums
of length n
. There will be at least one valid answer for the given n
.
Example 1:
Input: n = 4
Output: [2,1,4,3]
Example 2:
Input: n = 5
Output: [3,1,2,5,4]
Constraints:
1 <= n <= 1000
1-2. 내용 번역
길이가 N인 배열이 있고, 이 배열은 조건에 맞아야 한다.
- 배열의 원소값은
[1, N]
범위의 수이다. - 배열의 인덱스를 i, k, j 라고 하고
i < k < j
를 만족할 때k인덱스의 원소 값 * 2 != i인덱스의 원소값 + j인덱스의 원소값
이여야 한다.
결과는 최소 1개 이상이 보장될 때 any 결과 하나를 output으로 출력해라.
2. 문제 이해
2-1. 내용 이해
배열 길이가 N이라고 할 때, 원소값이 1,2,...,N인 배열을 리턴해야 한다.
인덱스의 원소값 조건 문장은 예제로 이해하는 것이 필요했음
nums[5]*2 != nums[4] + nums[6]
이기도 하고,
nums[5]*2 != nums[1] + nums[6]
이기도 하다.
2-2. 접근법 생각
Topic의 태그가 Divide & Conquer 이였다.
양 옆의 합이 가운데의 2배가 아니면 된다.
...
무작위로 나열하고 확인하는 방식을 사용해볼까? constraint가 1000이라고 했으니 가능하지 않을까?
-> 시간복잡도 측정 불가를 넘어 랜덤발생기의 촉을 믿는 방법이다.
...
중간 수를 2배하면 뭐든 짝수가 될테니 양쪽의 합은 홀수면 되나?
-> [1,2,3,4,5]
를 예시로 사용하면 [1,3,2,4,5]
이렇게?
-> 이걸보니 한 칸씩 건너뛴 수들은 짝수 + 홀수 페어네? 좀 더 큰 수로 해볼까?
[1,2,3,4,5,6,7,8]
를 예시로 사용하면 [1,3,2,4,5,7,8]
--> 아맞다. 바로 이어진 3개 수가 아니라 띄엄띄엄 떨어진 수에도 적용해야지.. 4*2=3+5
니까 안되겠네...
...
풀이를 보자..!
2-3. 적용한 풀이
Java | Clear Thinking Process - Divide and Conquer :: WelchJ
접근 프로세스를 설명하고 있어서 나의 접근방식과 비교하게 되었고, 어떤 부분에서 더 심화시켰는지 확인하고 배울 수 있는 풀이였다. (나의 이해 흐름을 서술했기 때문에 나의 풀이는 위의 링크의 보충설명 정도로 생각하고, 명확한 풀이는 위의 링크를 참고하자.)
WelchJ의 풀이에서는 k인덱스의 원소 값 * 2 != i인덱스의 원소값 + j인덱스의 원소값
이 조건을 distance의 개념으로 생각하면서 논리가 전개된다.
num[k]*2 == num[i] + num[j]
를 다음의 식 num[k]-num[i] == num[j]-num[k] (i<k<j, num[i] < num[k] < num[j])
으로 생각해보면 이것은 k와 i, j 각각의 거리이다.
즉, 사이의 거리가 같으면 공식이 성립하므로 조건에 부합하지 않는다.
거리를 다르게 하기 위해서는 아래의 그림을 생각해 볼 필요가 있다.
거리가 달라지면 조건이 부합되는걸 볼 수 있다. 거리를 다르게 한다는 말은 위의 그림은 인덱스의 관점으로 바라보았지만, 인덱스 엘리먼트의 값의 관점에서도 동일하게 적용할 수 있기 때문에 관점의 이동이 필요하다.
이제 여기에서 거리를 다르게만 만들면 될 것 같다. 그런데 이것은 알고리즘이고, 어떤 규칙이 꼭 있어야 하니까 기준을 만들어줘야 한다. 같은 거리의 인덱스, 그것들이 가지고 있는 각각의 엘리먼트의 거리는 달라야한다. 여기서 하나의 규칙을 더 찾아야 하는데, 같은 거리의 인덱스의 숫자를 보자. 두 수가 모두 홀수 혹은 짝수이다. 그럼 하나는 홀수, 하나는 짝수로 고르게 하는것을 기준으로 삼자(1).
*(1) 위에서도 홀수와 짝수를 가장 처음 생각하기는 했지만, 홀수와 짝수를 거리를 다르게하기 위한 기준이 된다는것으로 이어지지 않았다.
그럼 이제 num[i], num[j]
는 각각 모두 홀수, 모두 짝수가 되어야 한다. 그리고 num[k]
는 num[i] or num[j]
편 중 하나에 속한 어떤 엘리먼트일 것이다. 그럼 여기서 그룹이라는 개념이 나오게 된다.
[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]
.
9를 num[k]
로 선택하면 num[i]
는 num[k]
와의 거리가 모두 짝수가 되고, num[j]
는 모두 홀수가 되기 때문에 모든 조건에 부합한다.
하지만 각각의 그룹 안에서는 여전히 동일한 거리가 나올 수 있다.
위의 과정을 조금 더 깊게 보면 맨 처음 배열의 거리는 1+1*0, 1+1*1, 1+1*2, ... 이다.
둘로 나눈 배열의 거리는 2+2*0, 2+2*1, 2+2*2,... 이다.
곱해지는 수를 보면 해당 거리는 0, 1, 2로 처음과 똑같이 구분되는것을 알 수 있다. 그렇다면 나눈 배열에도 위의 조건을 동일하게 적용하자(2).
*(2) 0, 1, 2가 거리적인 의미에서 차이를 보완할 수 없게 만든다는 것을 이해하는데에 시간이 오래 걸렸다.
이렇게 되면 일반적인 분할 조건, 정렬 조건이 모두 나왔다. 이것을 구현하면 된다.
3. 구현
//O(nlogn)
class Solution {
fun beautifulArray(n: Int): IntArray {
val nums = IntArray(n)
for(i in 0..n-1) {
nums[i] = i+1
}
return dnq(nums)
}
fun dnq(nums: IntArray): IntArray {
if (nums.size ==1) return nums
//n == 7 -> 0, 1, 2, 3, 4, 5, 6
val rightSize = nums.size/2
val leftNums = IntArray(nums.size-rightSize) //leftSize == 4
val rightNums = IntArray(rightSize) //rightSize == 3
var idx = 0
while(idx < leftNums.size) { // 0, 1, 2, 3
leftNums[idx] = nums[idx*2] //0, 2, 4, 6
idx++
}
idx = 0
while(idx < rightNums.size) { //0, 1, 2
rightNums[idx] = nums[idx*2+1] //1, 3, 5
idx++
}
return dnq(leftNums) + dnq(rightNums)
}
}